package vsvm.data.filter.ranking;

import java.util.HashSet;
import java.util.Iterator;

import vsvm.math.statistics.Characteristics;

public class EntropyMeasure extends AbstractUnsupervisedRanking {

	private static final long serialVersionUID = 100L;
	private boolean HammingDistance;
	private int leastImportant;
	
	public EntropyMeasure()
	{
		HammingDistance = false;
		leastImportant = -1;
	}

	public EntropyMeasure(boolean b)
	{
		HammingDistance = b;
		leastImportant = -1;
	}
	
	public EntropyMeasure(boolean b, int leastImportant)
	{
		HammingDistance = b;
		this.leastImportant = leastImportant;
	}

	public double[] score() 
	{
		if(getLeastImportant() <= 0)
		{
			setLeastImportant(dataModel.getAttributeCount() + 10);
		}
			
		int numAttr = dataModel.getAttributeCount();
		int numVec  = dataModel.getVectorCount();
		
		min = new double[numAttr];
		max = new double[numAttr];
		
		for(int i = 0; i < numAttr; i++)
		{
			double[] vals = allFeatureValues(i);
			max[i] = Characteristics.getMax(vals);
			min[i] = Characteristics.getMin(vals);	
		}
		
		HashSet<Integer> mainSet = new HashSet<Integer>();
		mainSet.add(dataModel.getClassAttributeIndex());
		
		HashSet<Integer> set = new HashSet<Integer>();
		int rank = 1;
		double[] score = new double[numAttr];
		
		// leastImportant + 1 because of mainSet already contains class label 
		for(int i = 0; mainSet.size() < leastImportant + 1 
											&& mainSet.size() < numAttr - 1; i++)
		{			
			double[][] sim = similarity(numAttr, numVec, mainSet);
			
			double entropy = entropy(sim, numVec);

			Iterator<Integer> iter = mainSet.iterator();
			while(iter.hasNext())
			{
				set.add(iter.next());
			}			
//			System.out.println((double)(set.size()*100/numAttr)+"%");
			double[] diff = new double[numAttr];
			for(int j = 0; j < numAttr; j++)
			{
				double tmp = 0;
				if(!set.contains(j))
				{
					set.add(j);
					sim = similarity(numAttr, numVec, set);	
			
					tmp = entropy(sim, numVec);
					set.remove(j);
					diff[j] = Math.abs(entropy - tmp);
					
				} 
				else
				{
					diff[j] = Double.MAX_VALUE;		
				}
			}
			
			double min = Characteristics.getMin(diff);
			for(int t = 0; t < diff.length; t++)
			{
				if(min == diff[t] && !mainSet.contains(t))
				{
					mainSet.add(t);
					score[t] = rank;
				}
			}			
			rank++;
		}

		checkScore(score);
		
		return score;
	}
	
	private void checkScore(double[] score)
	{
		double max = Characteristics.getMax(score);
		for(int i = 0; i < score.length; i++)
		{
			if(score[i] == 0)
			{
				score[i] = max + 1;
			}
//			System.out.println(score[i]);
		}
	}
	
	private double[][] similarity(int numAttr, int numVec, HashSet<Integer> set)
	{
		double[][] sim = null;
		if(!HammingDistance)
		{
			sim = similarityEuclidean(numAttr, numVec, set);		
		}
		else
		{
			sim = similarityHamming(numAttr, numVec, set);			
		}
		
		return sim;
	}
	private double[][] similarityEuclidean(int attr, int vec, HashSet<Integer> set)
	{
		if(set.size() >= attr)
		{
			return null;
		}
		
		double[][] sim = new double[vec][vec];
		
		for(int i = 0; i < vec - 1; i++)
		{
			double[] vec1 = dataModel.getVector(i);

			for(int j = i + 1; j < vec; j++)
			{
				double distance = 0;
				double[] vec2 = dataModel.getVector(j);
				for(int k = 0; k < attr; k++)
				{
					if(!set.contains(k))
					{
						if(max[k] - min[k] > 1e-8)
						{
							distance += Math.pow((vec1[k] - vec2[k])/(max[k] - min[k]), 2);		
						}
					}
				}
				distance = Math.pow(distance, 0.5);
				sim[i][j] = Math.exp(-0.5 * distance);
				if(sim[i][j] <= 0 || sim[i][j] >= 1)
				{
//					incorrectSimilarityInfo("similarityEuclidean():", sim[i][j],
//														vec1, vec2, set, attr);
				}
			}
		}
		return sim;
	}
	
/*	private void incorrectSimilarityInfo(String name, double similarity, 
			double[] vec1, double[] vec2, HashSet<Integer> set, int attr)
	{
		System.err.println("Info: EntropyMeasure: " + name);
		System.err.println("Incorrect similarity value: " + similarity);
		System.err.println("Used vectors: ");
		for(int k = 0; k < attr; k++)
		{
			if(!set.contains(k))
			{
				if(max[k] - min[k] > 1e-8)
				{
					System.err.print("[" + vec1[k] + ", " + vec2[k] + "] ");		
				}
			}
		}
		System.err.println();
	}*/
	
	private double[][] similarityHamming(int attr, int vec, HashSet<Integer> set)
	{
		if(attr - set.size() >= 0)
		{
			return null;
		}
		
		double[][] sim = new double[vec][vec];
		for(int i = 0; i < vec - 1; i++)
		{
			double[] vec1 = dataModel.getVector(i);
			for(int j = i + 1; j < vec; j++)
			{
				double dist = 0;
				double ammount = 0;
				double[] vec2 = dataModel.getVector(j);
				for(int k = 0; k < attr; k++)
				{
					if(!set.contains(k))
					{		
						if(vec1[k] == vec2[k])
						{
							dist++;		
						}	
						ammount++;
					}
				}	
				sim[i][j] = dist/ammount;
				if(sim[i][j] <= 0 || sim[i][j] >= 1)
				{
//					incorrectSimilarityInfo("similarityHamming():", sim[i][j],
//														vec1, vec2, set, attr);
				}
			}
		}	
		return sim;
	}
	
	private double entropy(double sim[][], int numVec)
	{
		if(sim == null)
		{
			return 0;
		}
		
		double entropy = 0;
		for(int i = 0; i < numVec - 1; i++)
		{
			for(int j = i + 1; j < numVec; j++)
			{
				if(1 - sim[i][j] > 0 && sim[i][j] > 0)
				{
					entropy += (sim[i][j] * Math.log(sim[i][j]) + 
							(1 - sim[i][j]) * Math.log(1 - sim[i][j]));	
				}
			}
		}		
		return -entropy;
	}
	

	public boolean isHammingDistance() {
		return HammingDistance;
	}

	public void setHammingDistance(boolean hammingDistance) {
		HammingDistance = hammingDistance;
	}
	
	public int getLeastImportant() {
		return leastImportant;
	}

	public void setLeastImportant(int leastImportant) {
		this.leastImportant = leastImportant;
	}

}
